/* Copyright (c) 2008, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

namespace ElfLib
{
    class SectionType
    {
        public const UInt32 SHT_NULL = 0;
        public const UInt32 SHT_PROGBITS = 1;
        public const UInt32 SHT_SYMTAB = 2;
        public const UInt32 SHT_STRTAB = 3;
        public const UInt32 SHT_RELA = 4;
        public const UInt32 SHT_HASH = 5;
        public const UInt32 SHT_DYNAMIC = 6;
        public const UInt32 SHT_NOTE = 7;
        public const UInt32 SHT_NOBITS = 8;
        public const UInt32 SHT_REL = 9;
        public const UInt32 SHT_SHLIB = 10;
        public const UInt32 SHT_DYNSYM = 11;
    }

    class SectionFlags
    {
        public const UInt64 SHF_WRITE = 1;
        public const UInt64 SHF_ALLOC = 2;
        public const UInt64 SHF_EXECINSTR = 4;
    }

    class Elf32_Shdr
    {
        Elf32_Shdr() { throw new NotImplementedException(); }
    }

    class Elf64_Shdr
    {
        public UInt32 sh_name;
        public string Name;
        public UInt32 sh_type;
        public UInt64 sh_flags;
        public UInt64 sh_addr;
        public UInt64 sh_offset;

        public ushort index;

        public void SetName(string name, Elf64_String_Shdr strsect)
        {
            Name = name;
            sh_name = strsect.GetOffset(name);
        }

        public void AddTo(IList<Elf64_Shdr> shdrs)
        {
            this.index = Convert.ToUInt16(shdrs.Count);
            shdrs.Add(this);
        }

        public virtual UInt64 sh_size { get {
            if (data == null)
                return 0;
            if (data is ICollection<byte>) return (ulong)((ICollection<byte>)data).Count;
            UInt64 count = 0;
            foreach (byte b in data)
                count++;
            return count;
        }
        }
        public virtual IList<byte> data { get { return _data; } set { _data = value; } }
        public virtual UInt32 sh_link { get { return _sh_link; } set { _sh_link = value; } }
        public virtual UInt32 sh_info { get { return _sh_info; } set { _sh_info = value; } }
        public UInt64 sh_addralign = 4;
        public virtual UInt64 sh_entsize { get { return _sh_entsize; } set { _sh_entsize = value; } }

        public static int GetLength()
        { return 64; }

        private UInt32 _sh_link;
        private UInt32 _sh_info;
        private UInt64 _sh_entsize;
        private IList<byte> _data = new List<byte>();

        public void Write(Stream s)
        {
            Elf64Writer.Write(s, sh_name);
            Elf64Writer.Write(s, sh_type);
            Elf64Writer.Write(s, sh_flags);
            Elf64Writer.Write(s, sh_addr);
            Elf64Writer.Write(s, sh_offset);
            Elf64Writer.Write(s, sh_size);
            Elf64Writer.Write(s, sh_link);
            Elf64Writer.Write(s, sh_info);
            Elf64Writer.Write(s, sh_addralign);
            Elf64Writer.Write(s, sh_entsize);
        }
    }

    class Elf64_String_Shdr : Elf64_Shdr
    {
        private Dictionary<string, UInt32> strings = new Dictionary<string, UInt32>();

        public UInt32 GetOffset(string str)
        {
            try
            {
                return strings[str];
            }
            catch (KeyNotFoundException)
            {
                List<byte> data_list;
                if (data != null)
                    data_list = new List<byte>(data);
                else
                {
                    data_list = new List<byte>();
                    data_list.Add((byte)0);
                }
                uint offset = (uint)data_list.Count;
                byte[] new_str = Encoding.ASCII.GetBytes(str);
                data_list.AddRange(new_str);
                data_list.Add((byte)0);
                data = data_list.ToArray();
                strings.Add(str, offset);
                return offset;
            }
        }

        public Elf64_String_Shdr()
        {
            sh_type = SectionType.SHT_STRTAB;
            sh_flags = 0;
            sh_addr = 0;
            sh_link = 0;
            sh_info = 0;
            sh_addralign = 1;
            sh_entsize = 0;
            data = new byte[] { 0 };
        }
    }

    class Elf64_Null_Shdr : Elf64_Shdr
    {
        public Elf64_Null_Shdr()
        {
            sh_type = SectionType.SHT_NULL;
            sh_flags = 0;
            sh_addr = 0;
            sh_link = 0;
            sh_info = 0;
            sh_addralign = 0;
            sh_entsize = 0;
            sh_name = 0;
        }
    }

    class Elf64_Symbol_Shdr : Elf64_Shdr
    {
        public class Elf64_Sym : IEquatable<Elf64_Sym>
        {
            public class BindingFlags
            {
                public const byte STB_LOCAL = 0;
                public const byte STB_GLOBAL = 0x10;
                public const byte STB_WEAK = 0x20;
            }

            public class SymbolTypes
            {
                public const byte STT_NOTYPE = 0;
                public const byte STT_OBJECT = 1;
                public const byte STT_FUNC = 2;
                public const byte STT_SECTION = 3;
                public const byte STT_FILE = 4;
            }

            public UInt32 st_name;
            public byte st_info;
            public byte st_other = 0;
            public UInt16 st_shndx;
            public UInt64 st_value;
            public UInt64 st_size;

            public string name;

            public static int GetLength()
            { return 24; }

            #region IEquatable<Elf64_Sym> Members

            public bool Equals(Elf64_Sym other)
            {
                if (other == null)
                    return false;
                if (other.name == this.name)
                    return true;
                return false;
            }

            #endregion

            public void Write(object s)
            {
                Elf64Writer.Write(s, st_name);
                Elf64Writer.Write(s, st_info);
                Elf64Writer.Write(s, st_other);
                Elf64Writer.Write(s, st_shndx);
                Elf64Writer.Write(s, st_value);
                Elf64Writer.Write(s, st_size);
            }
        }

        public List<Elf64_Sym> defined_syms = new List<Elf64_Sym>();
   
        public override UInt64 sh_entsize { get { return (ulong)Elf64_Sym.GetLength(); } }
        public override uint sh_info { get { return _sh_info; } }
        public uint _sh_info = 0;
        public override IList<byte> data
        {
            get
            {
                List<byte> ret = new List<byte>();
                foreach (Elf64_Sym sym in defined_syms)
                    sym.Write(ret);
                return ret;
            }
        }

        public Elf64_Symbol_Shdr(Elf64_Ehdr ehdr)
        {
            sh_type = SectionType.SHT_SYMTAB;
            sh_flags = 0;
            sh_addr = 0;
            sh_link = ehdr.e_symstrndx;
            sh_addralign = 8;
        }
    }

    class Elf64_Rela_Shdr : Elf64_Shdr
    {
        public class Elf64_Rela
        {
            public class RelocationType
            {
                public const UInt32 R_X86_64_NONE = 0;
                public const UInt32 R_X86_64_64 = 1;
                public const UInt32 R_X86_64_PC32 = 2;
                public const UInt32 R_X86_64_GOT32 = 3;
                public const UInt32 R_X86_64_PLT32 = 4;
                public const UInt32 R_X86_64_COPY = 5;
                public const UInt32 R_X86_64_GLOB_DAT = 6;
                public const UInt32 R_X86_64_JUMP_SLOT = 7;
                public const UInt32 R_X86_64_RELATIVE = 8;
                public const UInt32 R_X86_64_GOTPCREL = 9;
                public const UInt32 R_X86_64_32 = 10;
                public const UInt32 R_X86_64_32S = 11;
                public const UInt32 R_X86_64_16 = 12;
                public const UInt32 R_X86_64_PC16 = 13;
                public const UInt32 R_X86_64_8 = 14;
                public const UInt32 R_X86_64_PC8 = 15;
            }

            public Elf64_Rela(ulong offset, uint reloctype, long addend, string target)
            {

                reloc_target = target;
                r_offset = offset;
                reloc_type = reloctype;
                r_addend = addend;
            }

            public UInt64 r_offset;
            public UInt32 reloc_type;
            public UInt32 sym_ndx;
            public Int64 r_addend;

            public string reloc_target;

            public static int GetLength() { return 24; }

            public void Write(object s)
            {
                Elf64Writer.Write(s, r_offset);
                Elf64Writer.Write(s, reloc_type);
                Elf64Writer.Write(s, sym_ndx);
                Elf64Writer.Write(s, r_addend);
            }
        }

        public List<Elf64_Rela> relocs = new List<Elf64_Rela>();

        public override ulong sh_entsize { get { return (ulong)Elf64_Rela.GetLength(); } }
        public override IList<byte> data
        {
            get
            {
                List<byte> ret = new List<byte>();
                foreach (Elf64_Rela rel in relocs)
                    rel.Write(ret);
                return ret;
            }
        }

        public Elf64_Rela_Shdr(Elf64_Ehdr ehdr, ushort data_sect_ndx)
        {
            sh_type = SectionType.SHT_RELA;
            sh_flags = 0;
            sh_addr = 0;
            sh_link = ehdr.e_symsndx;
            sh_info = data_sect_ndx;
            sh_addralign = 8;
        }
    }
}
